using System;
using System.Collections.Generic;
using XNAGameEngine.Logs;

namespace XNAGameEngine.GameState
{
    /// <summary>
    /// <para>Container class the regulates the creation of game state objects.</para>
    /// <para>Prevents multiple states of the same type from being registered and created.</para>
    /// </summary>
    public sealed class GameStateRegistry
    {
        #region local fields

        //the state manager that contains this registry
        private ManagerGameState _stateManager;

        //container to store the association between the string value and the object type
        private Dictionary<string, Type> _registry;

        //list to keep track of what types of states are currently in existence
        private List<string> _existingStateList;

        #endregion

        #region constructors

        /// <summary>
        /// constructor initializes the local variables
        /// </summary>
        public GameStateRegistry(ManagerGameState stateManager)
        {
            _stateManager = stateManager;
            _registry = new Dictionary<string, Type>();
            _existingStateList = new List<string>();
            ManagerLogs.Write("GameStateRegistry: Created");
        }

        #endregion

        #region local methods

        /// <summary>
        /// <para>Add the key and value to the dictionary.</para>
        /// <para>Performs type checking and duplicate checkings. </para>
        /// <para>Throws exception if either check fails.</para>
        /// </summary>
        /// <param name="key">the string value to associate with the type</param>
        /// <param name="value">the type to associate with the string value</param>
        public void RegisterGameStateObject(string key, Type value)
        {
            if (!value.IsSubclassOf(typeof(GameState)))
                throw new SystemException("Cannot register this type. Type passed in is not of type: "
                    + typeof(ManagerGameState).ToString());

            if (KeyIsRegistered(key))
                throw new SystemException("string value already registered");

            if (_registry.ContainsValue(value))
                throw new SystemException("Type value already registered");

            //add it to the registry
            _registry.Add(key, value);
        }

        /// <summary>
        /// <para>Return an instance of an object based on the string passed in.</para>
        /// <para>Performs checks to validate that key is valid, registered, or if the state
        /// is already instantiated.</para>
        /// <para>Exception is thrown if any check fails.</para>
        /// </summary>
        /// <param name="key">the string referring to the object type to create</param>
        public GameState CreateStateInstance(string key)
        {
            if (_registry.Count == 0)
                throw new SystemException("No states registered. Register a state before starting up the "
                    + "game engine.");

            //check if key exists
            if (!KeyIsRegistered(key))
                throw new SystemException("string value does not exist\n");

            //check if a state is already in the list of existing states
            if (_existingStateList.Contains(key))
                throw new SystemException("Object type already instantiated: "
                    + _registry[key].ToString());

            //add the state to the list of existing states
            _existingStateList.Add(key);

            object[] args = new object[2];
            args[0] = key;
            args[1] = _stateManager;

            try
            {
                //return an instance of the state associated with the string passed in.
                return
                    (GameState)_registry[key].GetConstructors()[0].Invoke(args);
            }
            catch (Exception e)
            {
                throw new Exception(
                    "An exception was thrown in the constructor of the game" +
                    "state you are trying to create \n \n" + e.ToString());
            }
        }

        /// <summary>
        /// <para>State is no longer needed and can be cleaned up.</para>
        /// <para>Performs check to make sure the key is registered or if the state actually exists.</para>
        /// <para>Exceptions are thrown if either check fails.</para>
        /// </summary>
        /// <param name="key">the state type to unregister</param>
        public void DestroyStateInstance(string key)
        {
            //check if key exists
            if (!KeyIsRegistered(key))
                throw new SystemException("string value is not registered");

            //check if a state is already in the list of existing states
            if (!_existingStateList.Contains(key))
                throw new SystemException("Object type not instantiated: "
                    + _registry[key].ToString());

            //remove from the list
            _existingStateList.Remove(key);

            //invoke a garbage collection
            GC.Collect();
        }

        /// <summary>
        /// Returns whether or not the string value is already registered
        /// </summary>
        /// <param name="key">the string value to check</param>
        /// <returns>true if the string value is registered, false otherwise</returns>
        public bool KeyIsRegistered(string key)
        {
            return _registry.ContainsKey(key);
        }

        #endregion
    }
}